/*
 * jfhexbig.java
 *
 * Created on July 30, 2007, 10:13 AM
 */

/**
 * Multi-tabbed hex editor (for BIG files - 512 bytes at a time).
 *
 * @author  pquiring
 */

import javaforce.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.text.*;

public class jfhexbig extends javax.swing.JFrame implements FindEvent, ReplaceEvent, DocumentListener {

  /** Creates new form jfhexbig */
  public jfhexbig() {
    initComponents();
    initApp();
  }

  /** This method is called from within the constructor to
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        tabs = new javax.swing.JTabbedPane();
        jPanel1 = new javax.swing.JPanel();
        prev = new javax.swing.JButton();
        next = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        offset = new javax.swing.JTextField();
        block = new javax.swing.JTextField();

        FormListener formListener = new FormListener();

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        addWindowListener(formListener);
        addComponentListener(formListener);
        addWindowStateListener(formListener);

        tabs.addMouseListener(formListener);
        tabs.addChangeListener(formListener);
        tabs.addKeyListener(formListener);

        prev.setText("Prev Block");
        prev.addActionListener(formListener);

        next.setText("Next Block");
        next.addActionListener(formListener);

        jLabel1.setText("Offset:");

        jLabel2.setText("Block:");

        offset.setEditable(false);
        offset.setText("0x0");

        block.setEditable(false);
        block.setText("0");

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
                        .addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(offset, javax.swing.GroupLayout.DEFAULT_SIZE, 252, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(prev))
                    .addGroup(jPanel1Layout.createSequentialGroup()
                        .addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(block, javax.swing.GroupLayout.DEFAULT_SIZE, 257, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(next)))
                .addContainerGap())
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(prev)
                    .addComponent(jLabel1)
                    .addComponent(offset, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel2)
                    .addComponent(next)
                    .addComponent(block, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(tabs, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(tabs, javax.swing.GroupLayout.DEFAULT_SIZE, 308, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );

        pack();
    }

    // Code for dispatching events from components to event handlers.

    private class FormListener implements java.awt.event.ActionListener, java.awt.event.ComponentListener, java.awt.event.KeyListener, java.awt.event.MouseListener, java.awt.event.WindowListener, java.awt.event.WindowStateListener, javax.swing.event.ChangeListener {
        FormListener() {}
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            if (evt.getSource() == prev) {
                jfhexbig.this.prevActionPerformed(evt);
            }
            else if (evt.getSource() == next) {
                jfhexbig.this.nextActionPerformed(evt);
            }
        }

        public void componentHidden(java.awt.event.ComponentEvent evt) {
        }

        public void componentMoved(java.awt.event.ComponentEvent evt) {
            if (evt.getSource() == jfhexbig.this) {
                jfhexbig.this.formComponentMoved(evt);
            }
        }

        public void componentResized(java.awt.event.ComponentEvent evt) {
            if (evt.getSource() == jfhexbig.this) {
                jfhexbig.this.formComponentResized(evt);
            }
        }

        public void componentShown(java.awt.event.ComponentEvent evt) {
        }

        public void keyPressed(java.awt.event.KeyEvent evt) {
            if (evt.getSource() == tabs) {
                jfhexbig.this.tabsKeyPressed(evt);
            }
        }

        public void keyReleased(java.awt.event.KeyEvent evt) {
        }

        public void keyTyped(java.awt.event.KeyEvent evt) {
        }

        public void mouseClicked(java.awt.event.MouseEvent evt) {
            if (evt.getSource() == tabs) {
                jfhexbig.this.tabsMouseClicked(evt);
            }
        }

        public void mouseEntered(java.awt.event.MouseEvent evt) {
        }

        public void mouseExited(java.awt.event.MouseEvent evt) {
        }

        public void mousePressed(java.awt.event.MouseEvent evt) {
        }

        public void mouseReleased(java.awt.event.MouseEvent evt) {
        }

        public void windowActivated(java.awt.event.WindowEvent evt) {
        }

        public void windowClosed(java.awt.event.WindowEvent evt) {
        }

        public void windowClosing(java.awt.event.WindowEvent evt) {
            if (evt.getSource() == jfhexbig.this) {
                jfhexbig.this.formWindowClosing(evt);
            }
        }

        public void windowDeactivated(java.awt.event.WindowEvent evt) {
        }

        public void windowDeiconified(java.awt.event.WindowEvent evt) {
        }

        public void windowIconified(java.awt.event.WindowEvent evt) {
        }

        public void windowOpened(java.awt.event.WindowEvent evt) {
        }

        public void windowStateChanged(java.awt.event.WindowEvent evt) {
            if (evt.getSource() == jfhexbig.this) {
                jfhexbig.this.formWindowStateChanged(evt);
            }
        }

        public void stateChanged(javax.swing.event.ChangeEvent evt) {
            if (evt.getSource() == tabs) {
                jfhexbig.this.tabsStateChanged(evt);
            }
        }
    }// </editor-fold>//GEN-END:initComponents

  private void formComponentMoved(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentMoved
//    System.out.println("moved");
    if (Settings.bWindowMax) return;
    Point loc = getLocation();
    Settings.WindowXPos = loc.x;
    Settings.WindowYPos = loc.y;
  }//GEN-LAST:event_formComponentMoved

  private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
//    System.out.println("resized");
    if (Settings.bWindowMax) return;
    Dimension size = getSize();
    Settings.WindowXSize = size.width;
    Settings.WindowYSize = size.height;
  }//GEN-LAST:event_formComponentResized

  private void formWindowStateChanged(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowStateChanged
//    System.out.println("stateChanged");
    Settings.bWindowMax = evt.getNewState() == MAXIMIZED_BOTH;
  }//GEN-LAST:event_formWindowStateChanged

  private void tabsKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_tabsKeyPressed
    //Key Pressed
    int f1 = evt.getKeyCode();
    int f2 = evt.getModifiers();
    int idx;
    if ((f1 == KeyEvent.VK_F1) && (f2 == 0)) {
      JOptionPane.showMessageDialog(this,
        "jfhexbig/" + JF.getVersion() + "\n\n" +
        "F1 = Help\n" +
        "F2 = Edit Settings\n" +
        "SPACE = Switch sides (hex/text)\n" +
        "CTRL-O = Open\n" +
        "CTRL-W = Close\n" +
        "CTRL-S = Save\n" +
//        "CTRL-Q = Save As\n" +
        "CTRL-F = Find (string)\n" +
        "CTRL-G/F3 = Find Again\n" +
        "CTRL-R = Replace (string)\n" +
        "CTRL-B = Find (binary)\n" +
        "CTRL-T = Replace (binary)\n" +
        "CTRL-L = Goto Byte Offset\n" +
        "CTRL-E = Execute Command\n" +
        "ALT-# = Switch to document\n\n"
        , "Help", JOptionPane.INFORMATION_MESSAGE);
      return;
    }
    if ((f1 == KeyEvent.VK_F2) && (f2 == 0)) {
      EditSettings.editSettings(this);
      Settings.fnt = JFAWT.getMonospacedFont(0, Settings.fontSize);
      Hex.changeFont(Settings.fnt);
      int cnt = pages.size();
      for(int a=0;a<cnt;a++) {
        pages.get(a).hex.repaint();
      }
    }
    if ((f1 == KeyEvent.VK_N) && (f2 == KeyEvent.CTRL_MASK)) { addpage("untitled", new byte[0]); return; }
    if ((f1 == KeyEvent.VK_S) && (f2 == KeyEvent.CTRL_MASK)) { savepage(); return; }
//    if ((f1 == KeyEvent.VK_Q) && (f2 == KeyEvent.CTRL_MASK)) { savepageas(); return; }
    if ((f1 == KeyEvent.VK_W) && (f2 == KeyEvent.CTRL_MASK)) { closepage(); return; }
    if ((f1 == KeyEvent.VK_O) && (f2 == KeyEvent.CTRL_MASK)) { openpage(); return; }
    if ((f1 == KeyEvent.VK_F) && (f2 == KeyEvent.CTRL_MASK)) { find(); return; }
    if ((f1 == KeyEvent.VK_G) && (f2 == KeyEvent.CTRL_MASK)) { findagain(false); return; }
    if ((f1 == KeyEvent.VK_F3) && (f2 == 0)) { findagain(false); return; }
    if ((f1 == KeyEvent.VK_R) && (f2 == KeyEvent.CTRL_MASK)) { replace(); return; }
    if ((f1 == KeyEvent.VK_L) && (f2 == KeyEvent.CTRL_MASK)) { gotopos(); return; }
    if ((f1 == KeyEvent.VK_E) && (f2 == KeyEvent.CTRL_MASK)) { execute(); return; }
    if ((f2 == KeyEvent.ALT_MASK) && (f1 >= KeyEvent.VK_0) && (f1 <= KeyEvent.VK_9)) {
      idx = f1 - KeyEvent.VK_0;
      if (idx == 0) idx = 9; else idx--;
      if (idx >= pages.size()) return;
      tabs.setSelectedIndex(idx);
      return;
    }
    if ((f1 == KeyEvent.VK_B) && (f2 == KeyEvent.CTRL_MASK)) { find_bin(); return; }
    if ((f1 == KeyEvent.VK_T) && (f2 == KeyEvent.CTRL_MASK)) { replace_bin(); return; }
    if ((f2 == KeyEvent.ALT_MASK) && (f1 == KeyEvent.VK_MINUS)) tabs.setSelectedIndex(10);
    if ((f2 == KeyEvent.ALT_MASK) && (f1 == KeyEvent.VK_EQUALS)) tabs.setSelectedIndex(11);
  }//GEN-LAST:event_tabsKeyPressed

  private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
    exit();
  }//GEN-LAST:event_formWindowClosing

  private void prevActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevActionPerformed
    doPrevBlock();
  }//GEN-LAST:event_prevActionPerformed

  private void nextActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextActionPerformed
    doNextBlock();
  }//GEN-LAST:event_nextActionPerformed

  private void tabsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabsStateChanged
    updateStats(tabs.getSelectedIndex());
  }//GEN-LAST:event_tabsStateChanged

  private void tabsMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tabsMouseClicked
    int idx = tabs.getSelectedIndex();
    if (idx < 0 || idx >= pages.size()) return;
    Page page = pages.get(idx);
    page.hex.grabFocus();
    System.out.println("tabs clicked");
  }//GEN-LAST:event_tabsMouseClicked

  /**
   * @param args the command line arguments
   */
  public static String args[];
  public static void main(String args[]) {
    jfhexbig.args = args;
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new jfhexbig().setVisible(true);
      }
    });
  }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTextField block;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JButton next;
    private javax.swing.JTextField offset;
    private javax.swing.JButton prev;
    private javax.swing.JTabbedPane tabs;
    // End of variables declaration//GEN-END:variables
  private class Page {
    JPanel panel;
    JScrollPane scroll;
    Hex hex;
    boolean dirty;
    File filename;
    int block;
  };
  private Vector<Page> pages;
  private boolean bLoading = false;

  private void initApp() {
    setTitle("jfhexbig");
    pages = new Vector<Page>();
    loadcfg();
    setSize(Settings.WindowXSize, Settings.WindowYSize);
    setLocation(Settings.WindowXPos, Settings.WindowYPos);
    if (Settings.bWindowMax) setExtendedState(MAXIMIZED_BOTH);
    if (args != null) {
      for(int a=0;a<args.length;a++) loadpages(args[a]);
    }
    if (pages.size() == 0) addpage("untitled", new byte[0]);
    tabs.setSelectedIndex(0);
    pages.get(0).hex.grabFocus();
  }
  public void loadcfg() {
    XML xml = new XML();
    String filename = JF.getUserPath() + "/.jhex.xml";
    File file = new File(filename);
    if (!file.exists()) return;  //doesn't exist
    if (!xml.read(filename)) return;  //bad cfg
    if (!xml.root.name.equals("jhex")) return;  //bad cfg
    xml.writeClass(xml.root, new Settings());
    Settings.fnt = JFAWT.getMonospacedFont(0, Settings.fontSize);
  }
  public void savecfg() {
    XML xml = new XML();
    XML.XMLTag tag;
    xml.root.name = "jhex";
    xml.readClass(xml.root, new Settings());
    String filename = JF.getUserPath() + "/.jhex.xml";
    xml.write(filename);
  }
//find data
  private String findstr = "";
  private String repstr = "";
  private boolean findww;
  private boolean findcw;
  private boolean findbin = false;
//interface FindEvent
  public void findEvent(FindDialog dialog) {
    if (findbin) {
      findstr = binary2String(dialog.getText());
    } else {
      findstr = dialog.getText();
    }
    findww = dialog.getWhole();
    findcw = dialog.getCase();
    findagain(false);
  }
//interface ReplaceEvent
  public boolean findEvent(ReplaceDialog dialog) {
    if (findbin) {
      findstr = binary2String(dialog.getFindText());
    } else {
      findstr = dialog.getFindText();
    }
    findww = dialog.getWhole();
    findcw = dialog.getCase();
    boolean ret = replace_find();
    if (!ret) notfound();
    return ret;
  }
  public void replaceEvent(ReplaceDialog dialog) {
    if (findbin) {
      findstr = binary2String(dialog.getFindText());
      repstr = binary2String(dialog.getReplaceText());
    } else {
      findstr = dialog.getFindText();
      repstr = dialog.getReplaceText();
    }
    findww = dialog.getWhole();
    findcw = dialog.getCase();
    replace_replace();
  }
  public void replaceAllEvent(ReplaceDialog dialog) {
    if (findbin) {
      findstr = binary2String(dialog.getFindText());
      repstr = binary2String(dialog.getReplaceText());
    } else {
      findstr = dialog.getFindText();
      repstr = dialog.getReplaceText();
    }
    findww = dialog.getWhole();
    findcw = dialog.getCase();
    replace_all();
  }
  private Page addpage(String title, byte data[]) {
    Page page = new Page();
    page.panel = JFAWT.createJPanel(new GridLayout(), null);
/*
    page.panel.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseClicked(java.awt.event.MouseEvent evt) {
        System.out.println("clicked panel");
      }
    });
*/
    page.hex = new Hex(this);
    page.hex.addKeyListener(new java.awt.event.KeyAdapter() {
      public void keyPressed(java.awt.event.KeyEvent evt) {
        tabsKeyPressed(evt);
      }
    });
    page.hex.setData(data);
    page.hex.setCaretPosition(0);
    page.scroll = new JScrollPane(page.hex);
    page.scroll.addMouseListener(new java.awt.event.MouseAdapter() {
      public Hex hex;
      public void mouseClicked(java.awt.event.MouseEvent evt) {
//        System.out.println("clicked scroll");
        hex.grabFocus();
      }
      public MouseAdapter init(Hex hex) {
        this.hex = hex;
        return this;
      }
    }.init(page.hex));
    page.scroll.setViewportView(page.hex);
    page.panel.add(page.scroll);
    page.filename = new File(title);
    page.dirty = false;
    page.panel.setVisible(true);
    tabs.addTab(title, page.panel);
    tabs.setSelectedComponent(page.panel);
    pages.add(page);
    return page;
  }
  private int getidx() { return tabs.getSelectedIndex(); }
  private boolean savepage() {
    int idx = getidx();
    if (pages.get(idx).dirty == false) return true;
    if (pages.get(idx).filename.toString().equals("untitled")) {return false;}  //savepageas();}
    try {
      writeBlock(idx);
      tabs.setTitleAt(idx, pages.get(idx).filename.getName());
      pages.get(idx).dirty = false;
      return true;
    } catch (Exception e) {
      JOptionPane.showMessageDialog(this, "Failed to save '" + pages.get(idx).filename.toString() + "'", "Warning", JOptionPane.ERROR_MESSAGE);
      return false;
    }
  }
  private boolean closepage(int idx) {
    tabs.setSelectedIndex(idx);
    return closepage();
  }
  private boolean closepage() {
    int idx = getidx();
    if (pages.get(idx).dirty) {
      //confirm to Save : Yes/No/Cancel
      switch (JOptionPane.showConfirmDialog(this, "Do you wish to save '" + pages.get(idx).filename.toString() + "' ?", "Confirm",
        JOptionPane.YES_NO_CANCEL_OPTION)) {
        case JOptionPane.YES_OPTION:
          if (!savepage()) return false;
          break;
        case JOptionPane.NO_OPTION:
          break;
        default:
        case JOptionPane.CANCEL_OPTION:
          return false;
      }
    }
    pages.remove(idx);
    tabs.remove(idx);
    if (pages.size() == 0) addpage("untitled", new byte[0]);
    return true;
  }
  private void openpage() {
    int idx = getidx();
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getCurrentPath()));
    if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
      //check if current Page is "untitled" and !dirty
      if (pages.get(idx).filename.toString().equals("untitled") && pages.get(idx).dirty == false) {
        //load on current Page
        pages.get(idx).filename = chooser.getSelectedFile();
      } else {
        addpage(chooser.getSelectedFile().getAbsolutePath(), new byte[0]);
        idx = tabs.getTabCount() - 1;
      }
      loadpage(idx);
      pages.get(idx).hex.grabFocus();
    }
  }
  private void loadpages(String filespec) {
    File f = new File(filespec);
    if (f.isDirectory()) {
/*
      String files[] = f.list();
      if (files == null || files.length == 0) return;
      for(int a=0;a<files.length;a++) {
        addpage(files[a], "");
        loadpage(tabs.getTabCount() - 1);
      }
*/
    } else {
      addpage(filespec, new byte[0]);
      loadpage(tabs.getTabCount() - 1);
    }
  }
  private void loadpage(int idx) {
    byte txt[];
    if ((pages.get(idx).filename.toString().indexOf("*") != -1) || (pages.get(idx).filename.toString().indexOf("?") != -1)) {
      closepage(idx);
      return;
    }
    try {
      readBlock(idx);
      tabs.setTitleAt(idx, pages.get(idx).filename.getName());
    } catch (Exception e) {
      JOptionPane.showMessageDialog(this, "Failed to open '" + pages.get(idx).filename.toString() + "'", "Warning", JOptionPane.ERROR_MESSAGE);
      closepage(idx);
    }
  }
  private void readBlock(int idx) throws Exception {
    Page page = pages.get(idx);
    File file = page.filename;
    if (!file.exists()) throw new Exception("file not found");
    RandomAccessFile fis = new RandomAccessFile(file, "r");
    long len = fis.length();
    if (page.block * 512 > len) {fis.close(); throw new Exception("beyond eof");}
    byte[] txt = new byte[512];
    fis.seek(page.block * 512);
    int read = fis.read(txt);
    fis.close();
    if (read != 512) {
      byte txt2[] = new byte[read];
      System.arraycopy(txt, 0, txt2, 0, read);
      txt = txt2;
    }
    bLoading = true;
    setData(idx, txt);
    page.hex.setCaretPosition(0);
    bLoading = false;
  }
  private void writeBlock(int idx) throws Exception {
    Page page = pages.get(idx);
    byte tmp[] = page.hex.getData();
    File file = page.filename;
    if (!file.exists()) throw new Exception("file not found");
    RandomAccessFile fos = new RandomAccessFile(file, "rw");
    long len = fos.length();
    if (page.block * 512 > len) throw new Exception("write failed");
    fos.seek(page.block * 512);
    fos.write(tmp);
    fos.close();
  }
  private void updateStats(int idx) {
    if (idx < 0 || idx >= pages.size()) return;
    Page page = pages.get(idx);
    block.setText("" + page.block);
    offset.setText("" + page.block * 512);
  }
  private void doNextBlock() {
    int idx = getidx();
    Page page = pages.get(idx);
    if (page.dirty) {
      try {
        writeBlock(idx);
      } catch (Exception e) {
        JOptionPane.showMessageDialog(this, "Failed to save block", "Warning", JOptionPane.ERROR_MESSAGE);
        return;
      }
    }
    page.block++;
    try { readBlock(idx); } catch (Exception e) {page.block--;}
    updateStats(idx);
  }
  private void doPrevBlock() {
    int idx = getidx();
    Page page = pages.get(idx);
    if (page.dirty) {
      try {
        writeBlock(idx);
      } catch (Exception e) {
        JOptionPane.showMessageDialog(this, "Failed to save block", "Warning", JOptionPane.ERROR_MESSAGE);
        return;
      }
    }
    if (page.block == 0) return;
    page.block--;
    try { readBlock(idx); } catch (Exception e) {}
    updateStats(idx);
  }
  private void notfound() {
    JOptionPane.showMessageDialog(this, "Unable to find match", "Notice", JOptionPane.ERROR_MESSAGE);
  }
  private void find() {
    findbin = false;
    FindDialog.showFindDialog(this, false, findstr, findww, findcw, this);
  }
  private void find_bin() {
    findbin = true;
    FindDialog.showFindDialog(this, false, string2Binary(findstr), findww, findcw, this);
  }
  private boolean isChar(char ch) {
    //return true if ch is a char that would be a part of a whole word
    if ((ch >= 'a') && (ch <= 'z')) return true;
    if ((ch >= 'A') && (ch <= 'Z')) return true;
    if ((ch >= '0') && (ch <= '9')) return true;
    if (ch == '_') return true;
    return false;
  }
  private boolean findagain(boolean quiet) {
    Hex hex = pages.get(getidx()).hex;
    if (findstr == null) return false;
    int findstrlen = findstr.length();
    char cafind[] = findstr.toCharArray();
    byte data[] = hex.getData();
    int length = data.length;
    char ca[] = new char[length];
    for(int a=0;a<length;a++) ca[a] = (char)data[a];
    boolean ok;
    int pos = hex.getCaretPosition();
    if (hex.isSelect()) pos++;
    for(;pos<ca.length - findstrlen+1;pos++) {
      ok = true;
      for(int a=0;a<findstrlen;a++) {
        if (!findcw) {
          if (Character.toUpperCase(ca[pos + a]) != Character.toUpperCase(cafind[a])) {ok = false; break;}
        } else {
          if (ca[pos + a] != cafind[a]) {ok = false; break;}
        }
      }
      if (!ok) continue;
      //found a match
      if (findww) {
        if ((pos > 0) && (isChar(ca[pos-1]))) continue;  //not a whole word
        if ((pos + findstrlen < ca.length) && (isChar(ca[pos + findstrlen]))) continue;  //not a whole word
      }
      hex.select(pos, pos+findstrlen-1);
      return true;
    }
    if (!quiet) notfound();
    return false;
  }
  private void replace() {
    findbin = false;
    ReplaceDialog.showReplaceDialog(this, true, findstr, repstr, findww, findcw, this);
  }
  private void replace_bin() {
    findbin = true;
    ReplaceDialog.showReplaceDialog(this, true, string2Binary(findstr), string2Binary(repstr), findww, findcw, this);
  }
  private boolean replace_find() {
    return findagain(true);
  }
  private void replace_replace() {
    Hex hex = pages.get(getidx()).hex;
    hex.delete();
    hex.paste(repstr.toCharArray());
  }
  private void replace_all() {
    int cnt = 0;
    pages.get(getidx()).hex.setCaretPosition(0);
    while (true) {
      if (!replace_find()) break;
      replace_replace();
      cnt++;
    }
    JOptionPane.showMessageDialog(this,
      "Replaced " + cnt + " occurances"
      , "Info", JOptionPane.INFORMATION_MESSAGE);
  }
  private void gotopos() {
    String str;
    int pos;
    int idx = getidx();
    Page page = pages.get(idx);
    try {
      str = JOptionPane.showInputDialog(this, "Enter hex offset?",
        "Goto", JOptionPane.QUESTION_MESSAGE);
      if (str == null) return;
      pos = JF.atox(str);
      if (page.dirty) {
        try {
          writeBlock(idx);
        } catch (Exception e) {
          JOptionPane.showMessageDialog(this, "Failed to save block", "Warning", JOptionPane.ERROR_MESSAGE);
          return;
        }
      }
      page.block = pos / 512;
      pos -= (page.block * 512);
      readBlock(idx);
      page.hex.setCaretPosition(pos);
      updateStats(idx);
    } catch (Exception e1) {}
  }
  private boolean isActive(Process proc) {
    try {
      int exitValue = proc.exitValue();
      return false;
    } catch (Exception e) {
      return true;
    }
  }
  private void execute() {
    try {
      String str = JOptionPane.showInputDialog(this, "Enter OS command",
        "Execute", JOptionPane.QUESTION_MESSAGE);
      if (str == null) return;
      String strs[] = str.split(" ");
      ArrayList<String> list = new ArrayList<String>();
      for(int a=0;a<strs.length;a++) list.add(strs[a]);
      ProcessBuilder pb = new ProcessBuilder(list);
      pb.redirectErrorStream();
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      Process proc = pb.start();
//      proc.waitFor();  //dead locks often
      InputStream is = proc.getInputStream();
      while (isActive(proc)) {
        int fs = is.available();
        if (fs > 0) {
          byte data[] = new byte[fs];
          int read = is.read(data);
          baos.write(data, 0, read);
        }
      }
      int fs = is.available();
      if (fs > 0) {
        byte data[] = new byte[fs];
        int read = is.read(data);
        baos.write(data, 0, read);
      }
      if (baos.size() == 0) {
        JOptionPane.showMessageDialog(this, "No output", "Execute",
          JOptionPane.INFORMATION_MESSAGE);
        return;
      }
      Page page = addpage("output-" + strs[0], baos.toByteArray());
      page.hex.grabFocus();
      page.hex.setCaretPosition(0);
    } catch (Exception e) {
    }
  }
  private void exit() {
    //close all windows
    int cnt = pages.size();
    for(int a=0;a<cnt;a++) {
      if (!closepage()) return;
    }
    savecfg();
    System.exit(0);
  }
  public void changedUpdate(DocumentEvent e) {
    changed();
  }
  public void insertUpdate(DocumentEvent e) {
    changed();
  }
  public void removeUpdate(DocumentEvent e) {
    changed();
  }
  public void changed() {
    if (bLoading) return;
    int idx = getidx();
    if (idx < 0 || idx >= pages.size()) return;
    if (pages.get(idx).dirty == false) {
      pages.get(idx).dirty = true;
      tabs.setTitleAt(idx, pages.get(idx).filename.getName() + " *");
    }
  }
  public void setData(int idx, byte data[]) {
    pages.get(idx).hex.setData(data);
  }
  public String binary2String(String in) {
    //in = "XX..."
    StringBuffer out = new StringBuffer();
    char ch;
    int len = in.length();
    for(int a=0;a<len/2;a++) {
      ch = (char)JF.atox(in.substring(a*2,a*2+2));
      out.append(ch);
    }
    if (len % 2 == 1) {
      ch = (char)JF.atox(in.substring(len-1));
      out.append(ch);
    }
    return out.toString();
  }
  public String string2Binary(String in) {
    //in = "abc..."
    //out = "414243..."
    StringBuffer out = new StringBuffer();
    char ch;
    int len = in.length();
    for(int a=0;a<len;a++) {
      ch = in.charAt(a);
      if (ch < 10) out.append("0");
      out.append(Integer.toString(in.charAt(a), 16));
    }
    return out.toString();
  }
}
